﻿//#include <iostream>
#include <cstdlib>
#include "bpt.h"
#include <vector>
#include <algorithm>
#include <ctime>
#include <thread>
#include <iostream>
#include "semaphore.h"

using namespace std;

#define BASIC_DEBUG
#define SERIA_DEBUG
#define MUL_THREAD_DEBUG


#define INSERT_ACT 0
#define REMOVE_ACT 1
#define CONTAIN_ACT 2

int basic_bpt_test(BPTree& bpt, int itr_count, int amount, int data_start = 0);
void test_thread_func(BPTree* bpt, vector<int>* data, int action);

int main() {

#ifdef BASIC_DEBUG
	BPTree basic_test_bpt(64, 64);
	basic_bpt_test(basic_test_bpt, 5, 100000);
	basic_test_bpt.clear();
#endif // BASIC_DEBUG

		
#ifdef SERIA_DEBUG
	std::printf("<===SERIALIZING TEST===>\n");
	std::printf("preparing data...");
	string bp_file = "E:/tmp/test.bp";
	int data_num = 1000000;
	BPTree seria_test_bpt(64, 64);
	vector<unsigned long> seria_r_data(data_num, 0);
	for (int i = 0; i < data_num; i++) {
		seria_r_data[i] = i;
	}
	random_shuffle(seria_r_data.begin(), seria_r_data.end());
	for (int i = 0; i < data_num; i++) {
		seria_test_bpt.insert(seria_r_data[i], i);
	}
	std::printf("inserted %d data\n", data_num);
	time_t seria_start = time(nullptr);
	std::printf("checking for basic de/serialize...");
	seria_test_bpt.serialize(bp_file);
	seria_test_bpt.clear();
	if (!seria_test_bpt.load(bp_file)) {
		std::printf("no\nfailed to deserialize");
		return -1;
	}
	std::printf("cost: %ds...", (int)(difftime(time(0), seria_start)));
	std::printf("yes\n");

	std::printf("validating integrity...");
	for (int i = 0; i < data_num; i++) {
		if (!seria_test_bpt.contain(seria_r_data[i])) {
			std::printf("no");
			return -1;
		}
	}

	for (int i = 0; i < data_num / 2; i++) {
		seria_test_bpt.remove(seria_r_data[i]);
		if (seria_test_bpt.contain(seria_r_data[i])) {
			std::printf("no");
			return -1;
		}
	}

	for (int i = data_num / 2; i < data_num; i++) {
		if (!seria_test_bpt.contain(seria_r_data[i])) {
			std::printf("no");
			return -1;
		}
	}

	std::printf("yes\n");
	
	basic_bpt_test(seria_test_bpt, 1, 10000, data_num);
	seria_test_bpt.clear();

#endif // SERIA_DEBUG

#ifdef MUL_THREAD_DEBUG
	std::printf("<===Thread Safety Test===>\n");
	int thread_num = 10;
	printf("thread num: %d\n", thread_num);
	std::printf("checking for thread safety...");
	vector<thread> ths(thread_num);
	BPTree thread_test_bpt(32, 64);
	int thread_data_num = 10000;
	vector<vector<int>> all_data(thread_num);
	for (int i = 0; i < thread_num; i++) {
		all_data[i].resize(thread_data_num, 0);
		for (int j = 0; j < thread_data_num; j++) {
			all_data[i][j] = i * thread_data_num + j;
		}
	}
	// 批量插入
	for (int i = 0; i < thread_num; i++) {
		ths[i] = thread(test_thread_func, &thread_test_bpt, &(all_data[i]), INSERT_ACT);
	}
	for (auto& t : ths) {
		t.join();
	}

	// 检查
	for (int i = 0; i < thread_num; i++) {
		for (auto c : all_data[i]) {
			if (!thread_test_bpt.contain(c)) {
				printf("no\n");
				return -1;
			}
		}
	}

	// 批量删除
	for (int i = 0; i < thread_num; i++) {
		ths[i] = thread(test_thread_func, &thread_test_bpt, &(all_data[i]), REMOVE_ACT);
	}
	for (auto& t : ths) {
		t.join();
	}

	for (int i = 0; i < thread_num; i++) {
		for (auto c : all_data[i]) {
			if (thread_test_bpt.contain(c)) {
				printf("no\n");
				return -1;
			}
		}
	}

	//thread_test_bpt.print();
	std::printf("yes\n");
	basic_bpt_test(thread_test_bpt, 1, 10000);
	

#endif // MUL_THREAD_DEBUG

	std::printf("All done.\n");
	return 0;
}



void test_thread_func(BPTree* bpt, vector<int>* data, int action) {
	for (int i = 0; i < data->size(); i++) {
		if (action == INSERT_ACT)
			bpt->insert(data->at(i), 0);
		else if (action == REMOVE_ACT)
			bpt->remove(data->at(i));
		else
			bpt->contain(data->at(i));
	}
}


int basic_bpt_test(BPTree& bpt, int itr_count, int amount, int data_start) {
	// 测试
	/*int itr_count = 10;
	int amount = 1000000;
	int branchs = 32;
	int leaf_cap = 120;*/

	std::printf("Basic Test: itr-%d amount/itr-%d\n", itr_count, amount);

	vector<unsigned long> random_data(amount, 0);

	for (int i = 0; i < amount; i++) {
		random_data[i] = i + data_start;
	}

	clock_t total_start = clock();

	for (int itr = 0; itr < itr_count; itr++) {
		std::printf("Iteration %d:\n", itr + 1);
		random_shuffle(random_data.begin(), random_data.end());

		clock_t itr_start = clock();
		std::printf("checking for batch insert...");
		//bool checking_ret = true;
		for (auto c : random_data) {
			bpt.insert(c, c);
			if (!bpt.contain(c)) {
				std::printf("no");
				return -1;
			}
		}

		for (auto c : random_data) {
			if (!bpt.contain(c)) {
				std::printf("no");
				return -1;
			}
		}

		std::printf("yes\n");


		std::printf("checking for batch delete...");
		for (auto c : random_data) {
			bpt.remove(c);
			if (bpt.contain(c)) {
				std::printf("no");
				return -1;
			}
		}

		for (auto c : random_data) {
			if (bpt.contain(c)) {
				std::printf("no");
				return -1;
			}
		}

		/*if (bpt.size()) {
			std::printf("no");
			return -1;
		}*/

		std::printf("yes\n");

		std::printf("checking for mixed insert/delete...");

		int half_amount = amount / 2 + 1;

		for (int i = 0; i < half_amount; i++) {
			bpt.insert(random_data[i], random_data[i]);
			if (!bpt.contain(random_data[i])) {
				std::printf("no");
				return -1;
			}
		}

		for (int i = half_amount; i < amount; i++) {
			bpt.insert(random_data[i], i);
			if (!bpt.contain(random_data[i])) {
				std::printf("no");
				return -1;
			}
			bpt.remove(random_data[i - half_amount]);
			if (bpt.contain(random_data[i - half_amount])) {
				std::printf("no");
				return -1;
			}
		}

		for (int i = half_amount; i < amount; i++) {
			if (!bpt.contain(random_data[i])) {
				std::printf("no");
				return -1;
			}
			if (bpt.contain(random_data[i - half_amount])) {
				std::printf("no");
				return -1;
			}
		}

		std::printf("yes\n");


		std::printf("checking for clear...");
		bpt.clear();
		assert(!bpt.size());

		for (int i = half_amount; i < amount; i++) {
			if (bpt.contain(random_data[i - half_amount])) {
				std::printf("no");
				return -1;
			}
		}

		std::printf("yes\n");

		std::printf("Itr %d Cost: %.3fs\n", itr + 1, (clock() - itr_start) * 1.0f / CLOCKS_PER_SEC);
	}

	std::printf("Total Data: %d Action Mount: %d Cost: %.3fs\n", itr_count * amount, itr_count* amount*10, (clock() - total_start) * 1.0f / CLOCKS_PER_SEC);
}